﻿using System;
using System.ComponentModel;
using System.Collections;
using System.Collections.Generic;
using System.Threading;

using FileTreeSpace;

namespace TravModelSpace
{
    public enum DuplWorkListSorting { None, BySize, ByHash };

    public class DuplWorkItem : IComparable<DuplWorkItem>
    {
        long size;
        int index;
        public string FileName { get; set; }
        public string Hash { get; set; }
        public bool WasHashed { get; set; }

        public DuplWorkItem (long newSize, int newIndex)
        { size = newSize; index = newIndex; FileName = null; }

        public DuplWorkItem (int newIndex, long newSize, string newName, string newHash)
        { index = newIndex; size = newSize; FileName = newName; Hash = newHash; }

        public long Size
        { get { return size; } }

        public int Index
        { get { return index; } }

        // Support stable sorting by size.
        public int CompareTo (DuplWorkItem other)
        {
            if (size < other.size)
                return -1;
            else if (size > other.size)
                return 1;
            else
                return index - other.index;
        }
    }


    public class DuplHashComparer : IComparer<DuplWorkItem>
    {
        public int Compare (DuplWorkItem row1, DuplWorkItem row2)
        {
            if (row1.Hash == null)
            {
                if (row2.Hash != null)
                    return -1;
            }
            else
                if (row2.Hash == null)
                    return 1;
                else
                {
                    int result = row1.Hash.CompareTo (row2.Hash);
                    if (result != 0)
                        return result;
                }
            return row1.Index - row2.Index;
        }
    }


    internal class DuplWorkList
    {
        private List<DuplWorkItem> data;

        public DuplWorkListSorting Ordering { get; private set; }

        public DuplWorkList ()
        {
            Ordering = DuplWorkListSorting.None;
            data = new List<DuplWorkItem> ();
        }

        public int Count { get { return data.Count; } }
        public DuplWorkItem this[int index] { get { return data[index]; } }

        public void Add (DuplWorkItem item)
        {
            data.Add (item);
        }

        public void SortByHash ()
        {
            if (Ordering != DuplWorkListSorting.ByHash)
            {
                Ordering = DuplWorkListSorting.ByHash;
                data.Sort (new DuplHashComparer ());
            }
        }

        // Sorts file list by file size.
        // On exit: TotalSize, TotalCount contain size & count of unhashed candidate duplicates. BUT ARE NEVER USED
        public void SortBySize ()
        {
            if (Ordering != DuplWorkListSorting.BySize)
            {
                Ordering = DuplWorkListSorting.BySize;
                data.Sort ();
            }
        }

        /// <summary>
        /// Yields unhashed files with size same as another file. 
        /// </summary>
        public IEnumerable ItemsToHash
        {
            get
            {
                SortBySize ();

                if (data.Count > 1)
                {
                    long prevSize = data[0].Size;

                    for (int i = 1; i < Count; ++i)
                    {
                        if (data[i].Size == prevSize)
                        {
                            if (data[i - 1].Hash == null)
                                yield return data[i - 1];

                            for (; ; )
                            {
                                if (data[i].Hash == null)
                                    yield return data[i];

                                if (++i >= Count)
                                    yield break;

                                if (data[i].Size != prevSize)
                                    break;
                            }
                        }
                        prevSize = data[i].Size;
                    }
                }
            }
        }
    }


    public partial class TravModel
    {
        private void finderWork (object sender, DoWorkEventArgs e)
        {
            BackgroundWorker thread = (BackgroundWorker) sender;
            DuplWorkList list = (DuplWorkList) e.Argument;

            int hashTotalCount = 0;
            long hashTotalSize = 0;
            int hashSoFarCount = 0;
            long hashSoFarSize = 0;

            // Calculate totals
            foreach (DuplWorkItem item in list.ItemsToHash)
            {
                ++hashTotalCount;
                hashTotalSize += item.Size;
            }

            // Hash the unhashed
            foreach (DuplWorkItem item in list.ItemsToHash)
            {
                if (thread.CancellationPending)
                {
                    e.Cancel = true;
                    return;
                }

                string s = String.Format (@"Hashing {0}/{1}: {2}",
                    hashSoFarCount + 1, hashTotalCount, item.FileName);

                if (hashTotalSize > 0)
                    thread.ReportProgress ((int) ((hashSoFarSize * 100) / hashTotalSize), s);

                DirPath path = new DirPath (Tree, item.FileName);
                path.CalcHash ();
                item.Hash = path.File.Hash.ToString ();
                item.WasHashed = true;

                ++hashSoFarCount;
                hashSoFarSize += item.Size;
            }

            e.Result = list;
        }


        private void finderProgressed (object sender, ProgressChangedEventArgs args)
        {
            string s = args.UserState as string;
            if (s != null)
                Progress.Caption = s;

            Progress.SetMeter (args.ProgressPercentage);
        }


        private void finderCompleted (object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                Presenter.ShowMessage (e.Error.Message, "IMPOSSIBLE ERROR");
                return;
            }

            Progress.Dismiss ();

            if (e.Cancelled)
                return;

            int ii;
            DuplWorkList list = e.Result as DuplWorkList;

            list.SortByHash ();

            for (ii = 0; ii < list.Count; ++ii)
            {
                if (list[ii].Hash != null)
                    break;
                Detail.Presenter.SetIsSelected (list[ii].Index, false);
            }

            while (ii < list.Count)
            {
                bool isDuplicate = false;

                for (;;)
                {
                    Detail.Presenter.SetIsSelected (list[ii].Index, isDuplicate);

                    if (list[ii].WasHashed)
                        Detail.Presenter.SetHash (list[ii].Index, list[ii].Hash);

                    if (++ii >= list.Count)
                        return;

                    isDuplicate = list[ii].Hash == list[ii-1].Hash;
                    if (! isDuplicate)
                        break;
                }
            }
        }
    }
}
